(function(D){
    var addRelation,
        annotate,
        makeIntervals,
        makeHtml, 
        isATextNode, isAnElement, isASpanNode, isAPNode, isAStrongNode,
        isAEmNode, isASubNode, isASupNode, isAInlineFormatingNode,
        loadEditor,
        save,
        balance,
        deleteInterval,
        initTinymce,
        reload,
        editors = {},
        saveBtn, annotateBtn, termsInput,
        setupEditor, makeContextMenu;

    makeIntervals = function(editor){
        var body=editor.getBody(),
            nbChars=0,
            intervals=[],
            text="",
            processChilds;
        
        processChilds=function(elem, supNbChars){
            var nbChars = 0,
                subNbChars = 0;
        
            $.each(elem.childNodes, function(i,child){
                if(isATextNode(child)){
                    nbChars += child.textContent.length;
                    text += child.textContent;
                }else if(isAInlineFormatingNode(child)){
                    text += "<" + child.tagName + ">";
                    nbChars += processChilds(child, 
        			     supNbChars + 
        			     nbChars + 
        			     "<>".length + 
        			     child.tagName.length);
                    nbChars += "<>".length + "</>".length + 2*child.tagName.length;
                    text += "</" + child.tagName + ">";
        	    }else if(isASpanNode(child)){
        	        subNbChars = 0;
        	        // la suppression d'une annotation peut laisser un SPAN vide :
        	        if(child.innerHTML != ""){
        	            subNbChars = processChilds(child, supNbChars + nbChars);
        	            intervals.push({
        	                start: supNbChars + nbChars + 1,
        	                end: supNbChars + nbChars + subNbChars,
        	                class: child.className,
        	                id: child.id
        	            });
        	        }
                    nbChars += subNbChars;
        	    }
            });
            return nbChars;
        }
        $.each(body.childNodes, function(i,child){
            if(isAPNode(child)){
        	    text += "<P>";
        	    nbChars += processChilds(child, nbChars + "<P>".length);
        	    nbChars += "<P></P>".length;
        	    text += "</P>";
            }
        });
        return {
            intervals: intervals,
            text: text
        };
    }

    makeHtml = function(intervals, text){
        var offsetStart=0,
            offsetEnd=0,
            tagOpenLength = '<span class="" id="">'.length,
            tagCloseLength = '</span>'.length;

        $.each(intervals, function(i,interval){
            var previousInterval,
                offsetStart=0,
                offsetEnd=0,
                openAndCloseOffset, openOffset;

            for(var j=0; j<i; j+=1){
                previousInterval = intervals[j];
                openAndCloseOffset = previousInterval.classNum.length + previousInterval.id.length + 
                                     tagOpenLength + tagCloseLength;
                openOffset = previousInterval.classNum.length + previousInterval.id.length + tagOpenLength;
                if(interval.start > previousInterval.end){offsetStart += openAndCloseOffset;}
                else if(interval.start > previousInterval.start){offsetStart += openOffset;}
                if(interval.end > previousInterval.end){offsetEnd += openAndCloseOffset;}
                else if(interval.end > previousInterval.start){ offsetEnd += openOffset; }
            }
            text = text.slice(0,offsetStart+interval.start-1)+
                '<span class="'+interval.classNum+'" id="'+interval.id+'">'+
                text.slice(offsetStart+interval.start-1, offsetEnd+interval.end)+
                '</span>' +
                text.slice(offsetEnd + interval.end);
        });
        return text;
    }

    isATextNode = function(node){
        return node.nodeType === 3;
    }
    isAnElement = function(tagName){
        return function(node){
            return ((node.nodeType === 1) && node.tagName === tagName);
        };
    }
    isASpanNode = isAnElement("SPAN");
    isAPNode = isAnElement("P");
    isAStrongNode = isAnElement("STRONG");
    isAEmNode = isAnElement("EM");
    isASubNode = isAnElement("SUB");
    isASupNode = isAnElement("SUP");
    isAInlineFormatingNode = function(node){
        return (isAStrongNode(node) || isAEmNode(node) ||
            isASubNode(node) || isASupNode(node));
    }

    loadEditor = function(edId){
        if(tinymce.get(edId)) tinymce.get(edId).remove();
        tinymce.execCommand("mceAddControl", true, edId);
    }

    initTinymce = function(){
        tinymce.init({
            mode:"none",
            theme:"advanced",
            forced_root_block: 'p',
            force_br_newlines: false,
            force_p_newlines: true,
            valid_elements: "@[id|class],span,p,sub,sup,strong,em",
            plugins:"noneditable",
            theme_advanced_buttons1:"bold, italic, sub, sup",
            theme_advanced_buttons2:"",
            theme_advanced_buttons3:"",
            theme_advanced_buttons4:"",
            theme_advanced_toolbar_location:"top",
            theme_advanced_toolbar_align:"left",
            theme_advanced_statusbar_location:"bottom",
            theme_advanced_resizing:true,
            content_css:"css/relations.css",
            setup: setupEditor   
        });
    }

    setupEditor = function(editor){
        editor.onInit.add(function(editor){
            var objId = editors[editor.editorId]['objId'],
                dbIntervals = editors[editor.editorId]['intervals'];
            D.request("annotation_get_text.txt",
                {
                    "id": objId
                }, function(results){
                    var initialText = "";

                    if(results.length === 1)
                    {
                        initialText = decodeURIComponent(results[0].text.value);
                        D.request("annotation_get_intervals.txt",
                            {
                                "id": objId
                            }, function(results){
                                dbIntervals = [];
                                $.each(results, function(i,r){
                                    dbIntervals.push({
                                        id: D.makeHtmlId(r.interval.value),
                                        start: parseInt(r.start.value),
                                        end: parseInt(r.end.value),
                                        term: r.term.value,
                                        class: r.classLabel.value,
                                        classNum: r.classId.value.replace(/.*\//,'')
                                    });
                                });
                                editor.setContent(makeHtml(dbIntervals, initialText));
                                $.each(dbIntervals, function(i,interval){
                                    makeContextMenu(interval.id, editor.editorId);
                                });
                            });
                    }else{
                        editor.setContent("");
                    }
            });
        });
    }

    makeContextMenu = function(intervalHtmlId,editorId){
        var elem=D.textFrame(editorId).find("#"+intervalHtmlId),
            transcriptionContainer = $("#"+editorId+"_parent"),
            contextMenuId = 'contextMenu'+intervalHtmlId,
            contextMenu,
            actions = {},
            intervalId = D.makeDBId(intervalHtmlId);
        $('#'+contextMenuId).remove();
        contextMenu = $('#contextMenu').clone().attr("id", contextMenuId).appendTo('body');
        D.request("getAnnotations.txt",
            {
                id: intervalId
            }, function(results){
                $.each(results, function(i,r){
                    var relationNum = 'rel'+r.relation.value.replace(/.*\//,''),
                        relationLabel = decodeURIComponent(r.relationLabel.value);
                    $('<li><a href="#'+relationNum+'">'+
                      relationLabel+'</a></li>').appendTo(contextMenu);
                    actions[relationNum]=function(){
                        D.annotator(r.annotationId.value);
                    };
                });
                elem.contextMenu({
                    menu: contextMenuId,
                    offset: function(){ 
                            var offset = transcriptionContainer.offset();
                            return {top: offset.top + 35, left: offset.left};
                        }
                    },
                    function(action, el){
                        actions['delete']=function(){
                            deleteInterval(intervalId, reload);       
                        };
                        actions['newNote']=function(){
                           $('<div id="dialog">'+
                             '<input id="relations" autocomplete="off"/>'+
                             '</div>').appendTo('body');
                            D.initRelations('relations', function(){
                                $('#dialog').dialog({
                                    height: 300,
                                    width: 350,
                                    modal: true,
                                    buttons: {
                                        'créer la relation': function(){
                                            var relLabel = $('#relations').val();
                                            if($.inArray(relLabel, D.relations) === -1){
                                                D.request('newAnnotation.txt', {
                                                    id: intervalId,
                                                    relation: D.relations[relLabel].id,
                                                    timestamp: D.timestamp(),
                                                    user: D.userName
                                                },function(){
                                                    loadEditor(editorId);
                                                },true);
                                            }
                                            $(this).dialog('close');
                                        }
                                    },
                                    close: function(){
                                        $('#dialog').remove();
                                    }
                                });
                            }, 'dialog');
                        };
                        actions[action]();
                    }
                );
            }
        );
    }

    balance = {
        c:0,
        f:false,
        inc:function(){
            this.c+=1;
        },
        dec:function(){
            this.c -= 1;
            if(this.f && this.c===0) this.f();
        },
        exec:function(fn){
            this.f=fn;
        }
    };

    save = function(edId){
        var relation,
            timestamp = D.timestamp(),
            editorIntervals = makeIntervals(tinymce.get(edId)),
            savedIntervals = editors[edId]['intervals'],
            newIntervals = editors[edId]['newIntervals'],
            bal = Object.create(balance);

        saveBtn.hide();
        $.each(editorIntervals.intervals, function(i,edInterval){
            var key;

            if((key = $.inArray(D.makeDBId(edInterval.id), newIntervals)) > -1){
                term = newIntervals[key];
                bal.inc();
                D.request("annotation_add_interval.txt",
                    {
                        id: editors[edId]['objId'],
                        timestamp: timestamp,
                        start: edInterval.start,
                        end: edInterval.end,
                        term: term,
                        user: D.userName
                    }, function(){bal.dec();}, true);
            }else{
                $.each(savedIntervals, function(j,savedInterval){
                    if(savedInterval.id === edInterval.id){
                        if(savedInterval.start != edInterval.start ||
                           savedInterval.end   != edInterval.end){
                            (function(){
                                var timestampbis = timestamp;
                                bal.inc();
                                D.request("updateInterval1.txt",
                                    {
                                        intervalId: D.makeDBId(edInterval.id)
                                    }, 
                                    function(){
                                        D.request("updateInterval2.txt",
                                            {
                                                intervalId: D.makeDBId(edInterval.id),
                                                start: edInterval.start,
                                                end: edInterval.end,
                                                timestamp: timestampbis,
                                                user: D.userName
                                            }, function(){bal.dec();}, true);
                                    }, true);
                            })();
                        }
                        savedIntervals.remElem(savedInterval);
                        return false;
                    }
                });
            }
            timestamp += 1;
        });
        newIntervals = [];
        $.each(savedIntervals, function(i,savInterval){
            var timestampbis = timestamp,
                id = D.makeDBId(savInterval.id);

            bal.inc();
            deleteInterval(id, function(){bal.dec();}, timestampbis);
            timestamp += 1;
        });
        bal.inc();
        bal.exec(function(){ saveBtn.show(); reload(); });
        D.request("annotation_delete_text.txt",
            {
                id: editors[edId]['objId']
            }, function(){
                D.request("annotation_add_text.txt",
                    {
                        id: editors[edId]['objId'],
                        text: encodeURIComponent(editorIntervals.text),
                        user: D.userName,
                        timestamp: timestamp + 1
                    }, function(){bal.dec();}, true);
            }, true);
    }

    deleteInterval = function(id, f, argTimestamp){
        var timestamp;
        if(argTimestamp){ timestamp=argTimestamp; }else{ timestamp = D.timestamp(); }
        D.request("deleteInterval1.txt",
            {
                "intervalId": id
            }, function(){
                D.request("deleteInterval2.txt",
                    {
                        "intervalId": id,
                        "user": D.userName,
                        "timestamp": timestamp
                    }, f, true);
            }, true);
    }

    annotate = function(edId){
        var item = termsInput.val(),
            t = D.terms[item],
            s = tinymce.get(edId).selection,
            c = s.getContent(),
            timestamp = D.timestamp();

        if(!t || c==="") return;
        editors[edId]['newIntervals'].push(t.id);
        s.setContent('<span class="'+t.classNum+'" id="'+D.makeHtmlId(t.id)+'">'+c+'</span>');
    }

    D.annotator = function(objId, controls){
        var textarea, controlLine, container;
        if(controls){
            edId = controls.edId;
            saveBtn = $('#'+controls.save);
            annotateBtn = $('#'+controls.annotate);
            termsInput = $('#'+controls.terms);
            D.initTerms(controls.terms);
        }else{
            container = $('<div class="annotation-container" style="position:absolute; top: 0; left: 0;"></div>'); 
            edId = 'annotation'+D.timestamp();
            textarea = $('<textarea id="'+edId+'"></textarea>').appendTo(container);
            controlLine = $('<div class="control-line"></div>').appendTo(container);
            saveBtn = $('<button>sauver</button>').appendTo(controlLine);
            termsInput = $('<input id="input'+edId+'" autocomplete="off"/>').appendTo(controlLine);
            annotateBtn = $('<button>annoter</button>').appendTo(controlLine);
            $('<button>fermer</button>').click(function(){ 
                container.remove(); 
            }).button().appendTo($('<div class="control-line"></div>').appendTo(container));
            container.appendTo('body');
            D.initTerms('input'+edId);
            container.draggable();
        }
        
        editors[edId] = {
            objId: objId, 
            intervals: [],
            newIntervals: []
        };

        saveBtn.button();
        saveBtn.click(function(){ save(edId); });

        annotateBtn.button();
        annotateBtn.click(function(){ annotate(edId); });
        
        initTinymce(edId);
        loadEditor(edId);
        reload = function(){ loadEditor(edId); };

        return {
            reload: reload,
            setId: function(id){ 
                editors[edId]['objId'] = id; 
                reload();
            }
        };
    };
})(DESANTI);
